#include "DataControl.h"

#include <QDebug>
#include <QMetaType>

// helper for uniform write size accounting
static inline quint64 effectiveBytes(qint64 w, int size) {
    return static_cast<quint64>(w > 0 ? w : size);
}

QSerialPort* DataControl::sm_inputPort = nullptr;

DataControl::DataControl(QObject *parent)
    : QObject{parent}
{
    // Register metatypes for queued signal delivery
    qRegisterMetaType<TransferStats>("TransferStats");
    qRegisterMetaType<EndpointStatsMap>("EndpointStatsMap");

    // Stats timer setup (start/stop with service)
    m_statsTimer = new QTimer(this);
    m_statsTimer->setInterval(1000);
    connect(m_statsTimer, &QTimer::timeout, this, &DataControl::onStatsTimerTick);
}

DataControl::~DataControl()
{
    cleanService();
}

void DataControl::initialization()
{}

bool DataControl::setInputPort(const PortConfig &portConfig)
{
    if (sm_inputPort == nullptr) {
        sm_inputPort = createSerialPort(portConfig, QIODevice::ReadWrite);
        if (!sm_inputPort) {
            emit error(QString(">>> Create Input Port %1 Failed! <<<").arg(portConfig.portName));
            cleanService();
            return false;
        }
        return true;
    }
    emit error(">>> The input port has been Exist <<<");
    cleanService();
    return false;
}

bool DataControl::addEndPointPort(const PortConfig &ePortConfig)
{
    QSerialPort* ePort = createSerialPort(ePortConfig, QIODevice::ReadWrite);
    if (!ePort) {
        emit error(QString(">>> EndPoint Port %1 Open Failed! <<<").arg(ePortConfig.portName));
        cleanService();
        return false;
    }
    m_EndPointsPortList.push_back(ePort);
    // Ensure per-endpoint stats entry exists
    auto &ep = m_perEndpointStats[ePort->portName()];
    Q_UNUSED(ep);
    return true;
}

bool DataControl::startService()
{
    if (!sm_inputPort) {
        emit error(QString(">>> The input port has not been create! <<<"));
        return false;
    }
    if (m_EndPointsPortList.size() == 0) {
        emit error(QString(">>> The endpoint port list is empty! <<<"));
        return false;
    }

    // qDebug() << ">>> Start Ports Data Forward <<<";

    connect(sm_inputPort, &QSerialPort::readyRead, this, &DataControl::onInputReadyRead);
    connect(sm_inputPort, &QSerialPort::errorOccurred, this, [this](QSerialPort::SerialPortError err){
        if (err != QSerialPort::NoError) {
            m_totalStats.totalErrors++;
        }
    });

    for (auto port : m_EndPointsPortList) {
        connect(port, &QSerialPort::readyRead, this, [this, port] {
            onOutputReadyRead(port);
        });
        connect(port, &QSerialPort::errorOccurred, this, [this, port](QSerialPort::SerialPortError err){
            if (err != QSerialPort::NoError) {
                auto &st = m_perEndpointStats[port->portName()];
                st.errorCount++;
                m_totalStats.totalErrors++;
            }
        });
    }

    resetStats();
    startStats();
    return true;
}

bool DataControl::stopService()
{
    // qDebug() << ">>> End Ports Data Forward <<<";

    disconnect(sm_inputPort, &QSerialPort::readyRead, this, &DataControl::onInputReadyRead);
    for (auto port : m_EndPointsPortList) {
        disconnect(port, &QSerialPort::readyRead, this, nullptr);
    }

    stopStats();
    cleanService();
    return true;
}

bool DataControl::cleanService()
{
    if (sm_inputPort) {
        sm_inputPort->deleteLater();
        sm_inputPort = nullptr;
    }
    while (!m_EndPointsPortList.isEmpty()) {
        auto port = m_EndPointsPortList.front();
        m_EndPointsPortList.pop_front();
        port->deleteLater();
    }
    return true;
}

bool DataControl::sendToInputPort(const QByteArray &data)
{
    if (sm_inputPort && sm_inputPort->isOpen()) {
        const qint64 w = sm_inputPort->write(data);
        m_totalStats.inputTxBytes += effectiveBytes(w, data.size());
        m_totalStats.inputTxPackets++;
        return true;
    }
    return false;
}

bool DataControl::sendToAllEndpoints(const QByteArray &data)
{
    forwardToOutputs(data);
    return true;
}

bool DataControl::sendToEndpointPort(const QString &portName, const QByteArray &data)
{
    bool isWrite = false;
    for (auto item : m_EndPointsPortList) {
        if (item->portName() == portName && item->isOpen()) {
            const qint64 w = item->write(data);
            const quint64 bytes = effectiveBytes(w, data.size());
            m_totalStats.outputsTxBytes += bytes;
            m_perEndpointStats[portName].txBytes += bytes;
            m_totalStats.outputsTxPackets++;
            m_perEndpointStats[portName].txPackets++;
            isWrite = true;
        }
    }
    return isWrite;
}

const QList<PortConfig> DataControl::endPortsList()
{
    QList<PortConfig> ports;
    for (const QSerialPort * port : m_EndPointsPortList) {
        if (!port) continue;
        PortConfig cfg(port->portName(), BaudRate(port->baudRate()));
        cfg.dataBits = port->dataBits();
        cfg.parity = port->parity();
        cfg.stopBits = port->stopBits();
        cfg.flowControl = port->flowControl();
        ports.append(cfg);
    }
    return ports;
}

QSerialPort* DataControl::createSerialPort(const PortConfig &portConfig, QIODevice::OpenModeFlag mode)
{
    QSerialPort *serialPort = new QSerialPort(this);
    serialPort->setPortName(portConfig.portName);
    serialPort->setBaudRate(portConfig.baudRate);
    serialPort->setDataBits(portConfig.dataBits);
    serialPort->setParity(portConfig.parity);
    serialPort->setStopBits(portConfig.stopBits);
    serialPort->setFlowControl(portConfig.flowControl);
    if (!serialPort->open(mode)) {
        serialPort->deleteLater();
        return nullptr;
    }
    return serialPort;
}

void DataControl::onInputReadyRead()
{
    if (sm_inputPort) {
        QByteArray data = sm_inputPort->readAll();
        m_totalStats.inputRxBytes += static_cast<quint64>(data.size());
        m_totalStats.inputRxPackets++;
        forwardToOutputs(data);
        emit InputPortDataReceived(data);
    }
}

void DataControl::onOutputReadyRead(QSerialPort* port)
{
    if (port) {
        QByteArray data = port->readAll();
        auto &st = m_perEndpointStats[port->portName()];
        const quint64 sz = static_cast<quint64>(data.size());
        st.rxBytes += sz;
        st.rxPackets++;
        m_totalStats.endpointsRxBytes += sz;
        m_totalStats.endpointsRxPackets++;
        forwardToInput(data);
        emit EndPointPortDataReceived(port->portName(), data);
    }
}

void DataControl::forwardToOutputs(const QByteArray &data)
{
    for (auto port : m_EndPointsPortList) {
        if (port->isOpen()) {
            const qint64 w = port->write(data);
            port->flush();
            const quint64 bytes = effectiveBytes(w, data.size());
            m_totalStats.outputsTxBytes += bytes;
            m_perEndpointStats[port->portName()].txBytes += bytes;
            m_totalStats.outputsTxPackets++;
            m_perEndpointStats[port->portName()].txPackets++;
        }
    }
}

void DataControl::forwardToInput(const QByteArray &data)
{
    if (sm_inputPort && sm_inputPort->isOpen()) {
        const qint64 w = sm_inputPort->write(data);
        sm_inputPort->flush();
        m_totalStats.inputTxBytes += effectiveBytes(w, data.size());
        m_totalStats.inputTxPackets++;
    }
}

void DataControl::startStats()
{
    m_lastStatsTs = QDateTime::currentDateTime();
    m_prevInputRxBytes = m_totalStats.inputRxBytes;
    m_prevInputTxBytes = m_totalStats.inputTxBytes;
    m_prevOutputsTxBytes = m_totalStats.outputsTxBytes;
    m_prevEndpointsRxBytes = m_totalStats.endpointsRxBytes;
    for (auto it = m_perEndpointStats.begin(); it != m_perEndpointStats.end(); ++it) {
        m_prevEpRxBytes[it.key()] = it.value().rxBytes;
        m_prevEpTxBytes[it.key()] = it.value().txBytes;
    }
    if (m_statsTimer) m_statsTimer->start();
}

void DataControl::stopStats()
{
    if (m_statsTimer) m_statsTimer->stop();
}

void DataControl::resetStats()
{
    m_totalStats = TransferStats{};
    m_prevInputRxBytes = 0;
    m_prevInputTxBytes = 0;
    m_prevOutputsTxBytes = 0;
    m_prevEndpointsRxBytes = 0;
    m_perEndpointStats.clear();
    m_prevEpRxBytes.clear();
    m_prevEpTxBytes.clear();
}

void DataControl::onStatsTimerTick()
{
    const QDateTime now = QDateTime::currentDateTime();
    double secs = m_lastStatsTs.msecsTo(now) / 1000.0;
    if (secs <= 0.0) secs = 1.0;

    // Overall Bps
    m_totalStats.inputRxBps = (m_totalStats.inputRxBytes - m_prevInputRxBytes) / secs;
    m_totalStats.inputTxBps = (m_totalStats.inputTxBytes - m_prevInputTxBytes) / secs;
    m_totalStats.outputsTxBps = (m_totalStats.outputsTxBytes - m_prevOutputsTxBytes) / secs;
    m_totalStats.endpointsRxBps = (m_totalStats.endpointsRxBytes - m_prevEndpointsRxBytes) / secs;

    m_prevInputRxBytes = m_totalStats.inputRxBytes;
    m_prevInputTxBytes = m_totalStats.inputTxBytes;
    m_prevOutputsTxBytes = m_totalStats.outputsTxBytes;
    m_prevEndpointsRxBytes = m_totalStats.endpointsRxBytes;

    // Per-endpoint Bps
    for (auto it = m_perEndpointStats.begin(); it != m_perEndpointStats.end(); ++it) {
        const QString &key = it.key();
        EndpointStats &st = it.value();
        const quint64 prevRx = m_prevEpRxBytes.value(key, 0);
        const quint64 prevTx = m_prevEpTxBytes.value(key, 0);
        st.rxBps = (st.rxBytes - prevRx) / secs;
        st.txBps = (st.txBytes - prevTx) / secs;
        m_prevEpRxBytes[key] = st.rxBytes;
        m_prevEpTxBytes[key] = st.txBytes;
    }

    m_totalStats.lastUpdated = now;
    emit StatsUpdated(m_totalStats, m_perEndpointStats);
    m_lastStatsTs = now;
}
